package com.hqd.jjvm.rtda.heap.ref;

import com.hqd.jjvm.rtda.heap.JClass;
import com.hqd.jjvm.rtda.heap.JMethod;
import com.hqd.jjvm.rtda.heap.classloader.JClassLoader;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.IOException;

@Getter
@Setter
@NoArgsConstructor
public class JInterfaceMethodRef extends JMemberRef {
	private JMethod jMethod;

	public JInterfaceMethodRef(JClass jClass, JInterfaceMethodRef jInterfaceMethodRef) throws ClassNotFoundException {
		super(jInterfaceMethodRef.getClassName(), jClass, jInterfaceMethodRef.getName(), jInterfaceMethodRef.getDescriptor());
		newJInterfaceMethodRef(jClass, jInterfaceMethodRef.getClassName(), jInterfaceMethodRef.getName(), jInterfaceMethodRef.getDescriptor());
	}

	public JInterfaceMethodRef(JClass currentClass, String className, String name, String descriptor) throws ClassNotFoundException {
		super(className, currentClass, name, descriptor);
		newJInterfaceMethodRef(currentClass, className, name, descriptor);
	}

	private void newJInterfaceMethodRef(JClass refJClass, String className, String methodName, String descriptor) throws ClassNotFoundException {
		JClassLoader loader = refJClass.getLoader();
		JClass jc = null;
		try {
			jc = loader.loadJClass(className);
		} catch (IOException e) {
			e.printStackTrace();
		}
		if (!jc.isInterface()) {
			throw new IncompatibleClassChangeError();
		}
		this.jMethod = lookupInterfaceMethod(jc, methodName, descriptor);
		if (this.jMethod == null) {
			throw new NoSuchMethodError();
		}
		if (!this.jMethod.isAccessibleTo(refJClass)) {
			throw new IllegalAccessError();
		}
	}

	private JMethod lookupInterfaceMethod(JClass jc, String methodName, String descriptor) {
		for (JMethod jMethod : jc.getJMethods()) {
			if (jMethod.getDescriptor().equals(descriptor) && jMethod.getName().equals(methodName)) {
				return jMethod;
			}
		}
		return lookupMethodInInterfaces(jc.getInterfaceClasses(), methodName, descriptor);
	}

	private JMethod lookupMethodInInterfaces(JClass[] interfaceClasses, String methodName, String descriptor) {
		for (JClass jc : interfaceClasses) {
			for (JMethod jm : jc.getJMethods()) {
				if (jm.getName().equals(methodName) && jm.getDescriptor().equals(descriptor)) {
					return jm;
				}
			}
			return lookupMethodInInterfaces(jc.getInterfaceClasses(), methodName, descriptor);
		}
		return null;
	}
}
